package logic.trails;

import fileHandling.language.options.OptionValues;
import gameStates.absGamesStates.AbsIngameState;

import java.util.HashMap;
import java.util.List;

import settings.GraphicSettings;

import logic.nodes.collision.CollidableNode;
import logic.nodes.lod.blocks.LeafBlock;

import com.jme.math.Vector3f;
import com.jme.scene.DistanceSwitchModel;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.SwitchModel;
import com.jme.scene.lod.DiscreteLodNode;
import com.jmex.effects.TrailMesh;

public abstract class AbstractTrail extends Node {
	
	private static final long serialVersionUID = 1L;
	
	protected float width;
	protected DiscreteLodNode lodNode;
	protected Node trailMeshNode;
	protected AbsIngameState ingameState;
	protected List<? extends Spatial> trailMeshes;
	
	protected int highSteps, medSteps, lowSteps;
	protected float delayInc;
	
	protected CollidableNode parentNode;
	protected boolean invisForced;
	protected TrailVisibility trailVis;
	
	protected TrailController trailController;
	protected HashMap<Integer, Float> delayMap;
	protected int currentChild;
		
	public AbstractTrail(CollidableNode parentNode, float width, AbsIngameState ingameState, 
			int highSteps, int medSteps, int lowSteps, float delayInc) {
		super("AbstractTrail");
		this.parentNode = parentNode;
		this.width = width;
		this.ingameState = ingameState;
		this.highSteps = highSteps;
		this.medSteps = medSteps;
		this.lowSteps = lowSteps;
		this.delayInc = delayInc;
		
		trailController = new TrailController(this);
		addController(trailController);
	}
	
	public abstract Vector3f getTrailFrontLoc();
	
	protected abstract List<TrailMesh> getHighTrailMeshes();
	
	protected abstract List<TrailMesh> getMediumTrailMeshes();
	
	protected abstract List<? extends Spatial> getLowTrailMeshes();
		
	public void checkVisibility() {
		if(invisForced) return;
		
		boolean vis = LeafBlock.isVisible(parentNode.getLocalTranslation(), ingameState);
		setVisible(vis);
	}
	
	protected void setVisible(boolean vis) {
		trailVis.setVisible(vis);
	}
	
	public void forceInvisibility(boolean force) {
		invisForced = force;
		if(invisForced) setVisible(false);
	}
	
	protected int getLODSteps() {
		String effectQual = GraphicSettings.get().getEffectsQualityString();
		if(effectQual.equals(OptionValues.High.toString())) return highSteps;
		else if(effectQual.equals(OptionValues.Medium.toString())) return medSteps;
		return lowSteps;
	}
	
	private List<? extends Spatial> getTrailMeshes() {
		String effectQual = GraphicSettings.get().getEffectsQualityString();
		if(effectQual.equals(OptionValues.High.toString())) return getHighTrailMeshes();
		else if(effectQual.equals(OptionValues.Medium.toString())) return getMediumTrailMeshes();
		return getLowTrailMeshes();
	}
	
	protected void init() {
		trailMeshes = getTrailMeshes();
		
		DistanceSwitchModel model = new DistanceSwitchModel();
		lodNode = getLODNode("Trail LOD Node", model);
		attachChild(lodNode);
		
		trailMeshNode = new Node("TrailMeshNode");
		
		Node parent = ingameState.getRootNode();
		Spatial child = trailMeshNode;
		
		for(Spatial c : trailMeshes) {
			if(c instanceof TrailMesh) {
				trailMeshNode.attachChild(c);
				((TrailMesh)c).resetPosition(getTrailFrontLoc());
			} else {
				parent = parentNode.getModel();
				parent.attachChild(c);
				child = c;
			}
		}
		
		trailVis = new TrailVisibility(parent, child);
		setVisible(true);
		
		delayMap = new HashMap<Integer, Float>();
		
		int steps = getLODSteps();
		float stepWidth = LeafBlock.particleDist / steps;
		
		float delay = 50f, minDist = 0f, maxDist = 0f;
		for(int i = 0; i < steps; i++) {
			maxDist += stepWidth;
			model.setModelDistance(i, minDist, maxDist);
						
			Node trailNode = new Node("" + i);
			lodNode.attachChildAt(trailNode, i);
			
			delayMap.put(Integer.valueOf(i), Float.valueOf(delay));
			
			minDist += stepWidth;
			delay -= delayInc;
		}
	}
	
	protected DiscreteLodNode getLODNode(String name, SwitchModel model) {
		return new DiscreteLodNode(name, model) {
			private static final long serialVersionUID = 1L;
			
			@Override
			public void setActiveChild(int child) {
				super.setActiveChild(child);
				if(trailMeshes.get(0) instanceof TrailMesh && child != currentChild && child >= 0) {
					currentChild = child;
					Integer i = Integer.valueOf(child);
					Float value = delayMap.get(i);
					float delay = value.floatValue();
					for(Spatial mesh : trailMeshes) {
						((TrailMesh)mesh).setUpdateSpeed(delay);
					}
				}
			}
		};
	}
	
	public void updateTrailMesh(TrailMesh mesh, Vector3f loc, float time) {
		mesh.setTrailFront(loc, width, time);
		mesh.update(ingameState.getCam().getLocation());
	}
	
	public void updateTrailMeshes(float time) {
		Vector3f loc = getTrailFrontLoc();
		
		for(Spatial mesh : trailMeshes) {
			if(mesh instanceof TrailMesh) updateTrailMesh((TrailMesh)mesh, loc, time);
		}
	}
	
	public void updateGraphicSettings() {
		deleteTrail();
		init();
	}
	
	public void deleteTrail() {
		if(trailVis.isVisible()) setVisible(false);
		detachAllChildren();
	}
}